﻿// Autodiscover.cs
//
// This file contains a proxy Autodiscover class.
//
// Copyright (C) 2007 Microsoft Corporation. All rights reserved.
//

// From Exchange Server 2007 SDK

namespace Microsoft.Samples.Office.UnifiedCommunications.Gadget.EWSUtility
{
    /// <summary>
    /// Proxy class to Exchange Autodiscover service
    /// </summary>

    public class Autodiscover
    {
        // Guid for SCP URL keyword
        private const string ScpUrlGuidString = @"77378F46-2C66-4aa9-A6A6-3E7A48B19596";

        // Guid for SCP pointer keyword
        private const string ScpPtrGuidString = @"67661d7F-8FC4-4fa7-BFAC-E1D7794C1F68";

        // Autodiscover path
        private const string AutodiscoverPath = @"/autodiscover/autodiscover.xml";

        // Enable verbose Console Tracing for debug build
#if DEBUG
        private const bool TraceToConsole = true;
#else
        private const bool TraceToConsole = false;
#endif

        /// <summary>
        /// Autodiscover the configurations for a specific e-mail address.
        /// </summary>
        /// <param name="emailAddress">SMTP address</param>
        /// <param name="nc">Network credentials, if null, then current credentials are used</param>
        /// <param name="maxHops">Maximum number of Active Directory and e-mail address redirections</param>
        /// <param name="allowSelfSignedCerts">Allow self-signed certificates flag</param>
        /// <returns>Autodiscover response</returns>
        /// 
        public static AutodiscoverResponseXml Discover(
           string emailAddress,
           System.Net.NetworkCredential nc,
           ref int maxHops,
           bool allowSelfSignedCerts)
        {
            CheckAndDecrementHops(ref maxHops);

            // Verify parameters.
            if (emailAddress == null)
            {
                throw new System.ArgumentNullException("E-mail address should not be null");
            }

            string[] emailAddressParts = emailAddress.Split('@');

            if ((emailAddressParts.Length < 2) || (string.IsNullOrEmpty(emailAddressParts[1])))
            {
                throw new System.ArgumentException("Invalid format for e-mail address: domain is not specified");
            }

            // Get the domain from the e-mail address.
            string domainName = emailAddressParts[1];

            // Get either a list of SCP URLs or a list with 0 entries.
            System.Collections.Generic.List<string> urls = GetScpUrlList(domainName, null, ref maxHops);

            // Create the Autodiscover response.
            AutodiscoverResponseXml autodiscoverResponse = null;

            // Try SCP URLs retrieved from Active Directory to make Autodiscover requests.
            if (urls.Count > 0)
            {
                // Get the Autodiscover response with client configuration information.
                autodiscoverResponse = DiscoverTryUrls(
                   emailAddress,
                   nc,
                   urls,
                   true,
                   allowSelfSignedCerts,
                   ref maxHops);

                // If a valid Outlook response is returned, stop the search.
                if (IsOutlookDataReturned(autodiscoverResponse))
                {
                    return autodiscoverResponse;
                }
                else
                {
                    // Clear the list of URLs to try two default URLs.
                    urls.Clear();
                }
            }

            // Add two SSL URLs built from the domain part of SMTP address.
            urls.Add("https://" + domainName + AutodiscoverPath);
            urls.Add("https://autodiscover." + domainName + AutodiscoverPath);

            ConsoleTrace("\n-- No response returned from SCP URLs, trying default URLs:");
            ConsoleTrace("+=  'https://" + domainName + AutodiscoverPath + "'");
            ConsoleTrace("+=  'https://autodiscover." + domainName + AutodiscoverPath + "'");

            // Try two SSL URLs.
            autodiscoverResponse = DiscoverTryUrls(
               emailAddress,
               nc,
               urls,
               false,
               allowSelfSignedCerts,
               ref maxHops);

            // If no response is returned yet
            if (!IsOutlookDataReturned(autodiscoverResponse))
            {
                // Try non-SSL URL to get SSL redirect URL.
                string redirectUrl = GetRedirectUrl(domainName);

                // If returned redirectUrl is not null it is a valid redirect SSL URL.
                // Then attempt to request Autodiscover data.
                if (!string.IsNullOrEmpty(redirectUrl))
                {
                    urls.Clear();
                    urls.Add(redirectUrl);

                    autodiscoverResponse = DiscoverTryUrls(
                       emailAddress,
                       nc,
                       urls,
                       false,
                       allowSelfSignedCerts,
                       ref maxHops);
                }
            }

            return autodiscoverResponse;
        }
        /// <summary>
        /// Search Active Directory for any related SCP URLs for a given domain name.
        /// </summary>
        /// <param name="domainName">Domain name to search for SCP information</param>
        /// <param name="ldapPath">LDAP path to start the search</param>
        /// <param name="maxHops">The number of remaining allowed hops</param>

        private static System.Collections.Generic.List<string> GetScpUrlList(
           string domainName,
           string ldapPath,
           ref int maxHops)
        {
            CheckAndDecrementHops(ref maxHops);

            ConsoleTrace("\n?? Starting SCP lookup for domainName='" + domainName + "', root path='" + ldapPath + "'");

            string scpUrl = null;
            string fallBackLdapPath = null;
            string rootDsePath = null;
            string configPath = null;

            // The container for SCP pointers and URLs objects from Active Directory
            System.DirectoryServices.SearchResultCollection scpDirEntries;

            // The list of SCP URLs.
            System.Collections.Generic.List<string> scpUrlList =
               new System.Collections.Generic.List<string>();

            try
            {
                // Get the LDAP root path.
                rootDsePath = (ldapPath == null) ? "LDAP://RootDSE" : ldapPath + "/RootDSE";

                // Get the root directory entry.
                System.DirectoryServices.DirectoryEntry rootDseEntry =
                   new System.DirectoryServices.DirectoryEntry(rootDsePath);

                // Get the configuration path.
                configPath = rootDseEntry.Properties["configurationNamingContext"].Value as string;

                // Get the configuration entry path.
                System.DirectoryServices.DirectoryEntry configEntry =
                   new System.DirectoryServices.DirectoryEntry("LDAP://" + configPath);

                // Use the configuration entry path to create a query.
                System.DirectoryServices.DirectorySearcher configSearcher =
                   new System.DirectoryServices.DirectorySearcher(configEntry);

                // Filter for Autodiscover SCP URLs and SCP pointers.
                configSearcher.Filter =
                   "(&(objectClass=serviceConnectionPoint)" +
                   "(|(keywords=" + ScpPtrGuidString + ")(keywords=" + ScpUrlGuidString + ")))";

                // Identify properties to retrieve using the the query.
                configSearcher.PropertiesToLoad.Add("keywords");
                configSearcher.PropertiesToLoad.Add("serviceBindingInformation");

                ConsoleTrace("?? Searching for SCP entries in " + configEntry.Path);

                // Query Active Directory for SCP entries.
                scpDirEntries = configSearcher.FindAll();
            }
            catch (System.Exception e)
            {
                ConsoleTrace("!! LDAP call failed with " + e.ToString());

                // Return the list with 0 entries.
                return scpUrlList;
            }

            // Identify the domain to match.
            string domainMatch = "Domain=" + domainName;

            // Contains a pointer to the LDAP path of a SCP object.
            string scpPtrLdapPath;

            ConsoleTrace("-? Scanning for SCP pointers, " + domainMatch);

            foreach (System.DirectoryServices.SearchResult scpDirEntry in scpDirEntries)
            {
                System.DirectoryServices.ResultPropertyValueCollection entryKeywords = scpDirEntry.Properties["keywords"];

                // Identify SCP pointers.
                if (Contains(entryKeywords, ScpPtrGuidString))
                {
                    // Get the LDAP path to SCP pointer.
                    scpPtrLdapPath = scpDirEntry.Properties["serviceBindingInformation"][0] as string;

                    // If the SCP pointer matches the user's domain, then restart search from that point.
                    if (Contains(entryKeywords, domainMatch))
                    {
                        // Stop the current search, start another from a new location.
                        ConsoleTrace("== SCP pointer for " + domainMatch + " is found in '" + scpDirEntry.Path +
                                  "'\n== restarting seach in '" + scpPtrLdapPath + "'");
                        return GetScpUrlList(domainName, scpPtrLdapPath, ref maxHops);
                    }
                    else
                    {
                        // Save the first SCP pointer ldapPath for a later call if a SCP URL is not found.
                        // Directory entries with a SCP pointer should have only one keyword=ScpPtrGuidString.
                        if ((entryKeywords.Count == 1) && string.IsNullOrEmpty(fallBackLdapPath))
                        {
                            fallBackLdapPath = scpPtrLdapPath;
                            ConsoleTrace("+= Fallback SCP pointer='" + fallBackLdapPath + "'for " + domainMatch + " is found in '" +
                                      scpDirEntry.Path + "' and saved");
                        }
                    }
                }
            }

            ConsoleTrace("-- No SCP pointers found for " + domainMatch + " in configPath='" + configPath + "'");

            // Get the computer's current site.
            string computerSiteName = null;

            try
            {
                computerSiteName = GetSiteName();
            }

            catch (System.Exception e)
            {
                ConsoleTrace("-! Info: unable to get computer site name\n" + e.ToString());
            }

            if (!string.IsNullOrEmpty(computerSiteName))
            {
                // Search for SCP entries.
                string sitePrefix = "Site=";
                string siteMatch = sitePrefix + computerSiteName;
                System.Collections.Generic.List<string> scpListNoSiteMatch =
                   new System.Collections.Generic.List<string>();

                ConsoleTrace("-? Scanning for SCP urls for the current computer " + siteMatch);

                foreach (System.DirectoryServices.SearchResult scpDirEntry in scpDirEntries)
                {
                    System.DirectoryServices.ResultPropertyValueCollection entryKeywords = scpDirEntry.Properties["keywords"];

                    // Identify SCP URLs.
                    if (Contains(entryKeywords, ScpUrlGuidString))
                    {
                        // Get the SCP URL.
                        scpUrl = scpDirEntry.Properties["serviceBindingInformation"][0] as string;

                        // If the SCP URL matches the exact ComputerSiteName.
                        if (Contains(entryKeywords, siteMatch))
                        {
                            // Priority 1 SCP URL. Add SCP URL to the list if it's not already there.
                            if (!Contains(scpUrlList, scpUrl))
                            {
                                ConsoleTrace("+=  adding (prio 1) '" + scpUrl + "' for the " + siteMatch +
                                          " from '" + scpDirEntry.Path +
                                          "' to the top of the list (exact match)");
                                scpUrlList.Add(scpUrl);
                            }
                        }

                        // No match between the SCP URL and the ComputerSiteName
                        else
                        {
                            bool hasSiteKeyword = false;

                            // Check if SCP URL entry has any keyword starting with "Site=" 
                            foreach (string keyword in entryKeywords)
                            {
                                hasSiteKeyword |= keyword.StartsWith(sitePrefix, System.StringComparison.InvariantCultureIgnoreCase);
                            }

                            // Add SCP URL to the scpListNoSiteMatch list if it's not already there.
                            if (!Contains(scpListNoSiteMatch, scpUrl))
                            {
                                // Priority 2 SCP URL. SCP entry doesn't have any "Site=<otherSite>" keywords, insert at the top of list.
                                if (!hasSiteKeyword)
                                {
                                    ConsoleTrace("+=  adding (prio 2) '" + scpUrl +
                                              "' from '" + scpDirEntry.Path +
                                              "' into the middle of the list (wildcard)");
                                    scpListNoSiteMatch.Insert(0, scpUrl);
                                }
                                // Priority 3 SCP URL. SCP entry has at least one "Site=<otherSite>" keyword, add to the end of list.
                                else
                                {
                                    ConsoleTrace("+=  adding (prio 3) '" + scpUrl +
                                              "' from '" + scpDirEntry.Path +
                                              "' to the end of the list (site mismatch)");
                                    scpListNoSiteMatch.Add(scpUrl);
                                }
                            }
                        }
                    }
                }

                // Append SCP URLs to the list. List contains:
                // Priority 1 URLs -- URLs with an exact match, "Site=<machineSite>"
                // Priority 2 URLs -- URLs without a match, no any "Site=<anySite>" in the entry
                // Priority 3 URLs -- URLs without a match, "Site=<nonMachineSite>"
                if (scpListNoSiteMatch.Count > 0)
                {
                    foreach (string url in scpListNoSiteMatch)
                    {
                        if (!Contains(scpUrlList, url))
                        {
                            scpUrlList.Add(url);
                        }
                    }
                }
            }

            // If no entries found, try fallBackLdapPath if it's non-empty.
            if (scpUrlList.Count == 0)
            {
                if (!string.IsNullOrEmpty(fallBackLdapPath))
                {
                    ConsoleTrace("== Restarting search for domain:" + domainName + " in SCP fallback pointer:" + fallBackLdapPath);
                    return GetScpUrlList(domainName, fallBackLdapPath, ref maxHops);
                }
            }

            // Return the list with 0 or more SCP URLs.
            return scpUrlList;
        }

        /// <summary>
        /// Autodiscover e-mail address against a list of URLs.
        /// </summary>
        /// <param name="emailAddress">SMTP address</param>
        /// <param name="nc">Network credentials</param>
        /// <param name="urls">List of URLs to try</param>
        /// <param name="authoritative">URLs are from Active Directory</param>
        /// <param name="allowSelfSignedCerts">Allow self signed certs flag</param>
        /// <param name="maxHops">The number of remaining Active Directory or e-mail address redirections</param>
        /// <returns></returns>

        private static AutodiscoverResponseXml DiscoverTryUrls(
           string emailAddress,
           System.Net.NetworkCredential nc,
           System.Collections.Generic.List<string> urls,
           bool authoritative,
           bool allowSelfSignedCerts,
           ref int maxHops)
        {
            AutodiscoverResponseXml responseXml = null;

            foreach (string url in urls)
            {
                // Try each URL - send request to Autodiscover and parse the response.
                responseXml = DiscoverTryUrl(emailAddress, nc, url, authoritative, allowSelfSignedCerts);

                // Check if the response contains data.
                if ((IsOutlookDataReturned(responseXml) && (responseXml.OutlookData.Account != null)))
                {
                    // Check if the response contains a redirect.
                    if (responseXml.OutlookData.Account.Action == "redirectAddr")
                    {
                        // Retry Autodiscover with new redirect e-mail address.
                        string redirectEmailAddress = responseXml.OutlookData.Account.RedirectAddr;
                        ConsoleTrace("== Redirect EmailAddress='" + redirectEmailAddress + "' is returned, restarting the search");
                        responseXml = Discover(redirectEmailAddress, nc, ref maxHops, allowSelfSignedCerts);
                    }

                    // Outlook account data is returned by the Autodiscover service - stop the search.
                    break;
                }

                // Check for error response.
                else
                {
                    if (IsErrorResponseReturned(responseXml))
                    {
                        ConsoleTrace("-! ErrorCode='" + responseXml.ErrorResponse.Error.ErrorCode +
                           "', Message='" + responseXml.ErrorResponse.Error.Message + "' is returned by Audiscover service");
                    }
                }
            }

            // Return the successful response.
            return responseXml;
        }

        /// <summary>
        /// Contact Autodiscover service at the given URL with the given e-mail address.
        /// </summary>
        /// <param name="emailAddress">SMTP address</param>
        /// <param name="nc">Network credentials</param>
        /// <param name="url">A single URL to try</param>
        /// <param name="authoritative">URL is from Active Directory</param>
        /// <param name="allowSelfSignedCerts">Allow self-signed certificates</param>
        /// <returns></returns>

        private static AutodiscoverResponseXml DiscoverTryUrl(
           string emailAddress,
           System.Net.NetworkCredential nc,
           string url,
           bool authoritative,
           bool allowSelfSignedCerts)
        {
            AutodiscoverResponseXml responseXml = null;
            ConsoleTrace("\n=? Trying '" + emailAddress + "' at '" + url + "'");
            try
            {
                // Custom handling of certificates.
                if (allowSelfSignedCerts)
                {
                    // Set the callback to handle certificates.
                    System.Net.ServicePointManager.ServerCertificateValidationCallback += CertificateValidationCallBack;
                }

                // Create WebRequest based on credentials and URL.
                System.Net.WebRequest webRequest = System.Net.HttpWebRequest.Create(url);
                if (nc == null)
                    webRequest.UseDefaultCredentials = true;
                else
                    webRequest.Credentials = nc;

                // Create Autodiscover request body to POST to Autodiscover service.
                webRequest.Method = "POST";
                webRequest.ContentType = "text/xml";

                System.IO.Stream webRequestStream = webRequest.GetRequestStream();
                SerializeRequest(emailAddress, webRequestStream);
                webRequestStream.Close();

                // Create WebResponse.
                System.Net.WebResponse webResponse;


                // Send the Autodiscover request, get the response.
                webResponse = webRequest.GetResponse();

                // Deserialize and get the Autodiscover response.
                responseXml = DeserializeResponse(webResponse.GetResponseStream());
            }

            catch (System.Net.WebException e)
            {
                if (authoritative)
                    ConsoleTrace("!! Error: " + e.ToString());
                else
                    ConsoleTrace("-! Info: " + e.ToString());
            }
            finally
            {
                if (allowSelfSignedCerts)
                {
                    // Remove the callback.
                    System.Net.ServicePointManager.ServerCertificateValidationCallback -= CertificateValidationCallBack;
                }
            }

            // Return the Autodiscover response.
            return responseXml;
        }

        /// <summary>
        /// Get redirection to SSL Autodiscover URL from non-SSL URL.
        /// </summary>
        /// <param name="emailAddress">SMTP address</param>
        /// <param name="nc">Network credentials</param>
        /// <param name="url">A single URL to try</param>
        /// <param name="authoritative">URL is from Active Directory</param>
        /// <returns>Valid redirect SSL URL or null</returns>

        private static string GetRedirectUrl(string domainName)
        {
            string url = "http://autodiscover." + domainName + AutodiscoverPath;

            ConsoleTrace("\n?? Trying GET redirect URL on non-SSL URL '" + url + "'");

            try
            {
                // Create the HTTP Web request.
                System.Net.HttpWebRequest httpRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);

                httpRequest.Method = "GET";
                httpRequest.AuthenticationLevel = System.Net.Security.AuthenticationLevel.None;
                httpRequest.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Anonymous;
                httpRequest.AllowAutoRedirect = false;
                httpRequest.PreAuthenticate = false;

                // Send the request and get the response.
                System.Net.HttpWebResponse httpResponse = (System.Net.HttpWebResponse)httpRequest.GetResponse();

                // Check if 301 or 302 codes are returned.
                if ((httpResponse.StatusCode == System.Net.HttpStatusCode.Moved) ||
                   (httpResponse.StatusCode == System.Net.HttpStatusCode.Redirect))
                {
                    // Get the redirect URL and verify that it's valid.
                    string redirectUrl = httpResponse.Headers["Location"];
                    bool isValidRedirectUrl = false;

                    if (!string.IsNullOrEmpty(redirectUrl))
                    {
                        System.UriBuilder urib = new System.UriBuilder(redirectUrl);

                        // Check if URI is SSL and that the path matches.
                        isValidRedirectUrl =
                           (string.Compare(urib.Scheme, "https", true) == 0) &&
                           (string.Compare(urib.Path, AutodiscoverPath, true) == 0);
                    }

                    // Return the redirect URL.
                    if (isValidRedirectUrl)
                    {
                        ConsoleTrace("+= '" + url + "' returns valid redirect url '" + redirectUrl + "'");
                        return redirectUrl;
                    }
                    else
                    {
                        ConsoleTrace("-! '" + url + "' returns invalid redirect url '" + redirectUrl + "'");
                    }
                }

                // The default non-SSL end point has not been setup to redirect.
                else
                {
                    ConsoleTrace("-! '" + url + "' is not configured for redirection");
                }
            }

            catch (System.Net.WebException e)
            {
                ConsoleTrace("!! Exception: " + e.ToString());
            }

            // No redirect URL available or it is invalid.
            return null;
        }

        /// <summary>
        /// Callback to handle SSL certificate.
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="certificate">Certificate</param>
        /// <param name="chain">Chain</param>
        /// <param name="sslPolicyErrors">SSL policy errors</param>
        /// <returns>true if certificate is OK</returns>
        private static bool CertificateValidationCallBack(
              object sender,
              System.Security.Cryptography.X509Certificates.X509Certificate certificate,
              System.Security.Cryptography.X509Certificates.X509Chain chain,
              System.Net.Security.SslPolicyErrors sslPolicyErrors)
        {
            // If no errors - exit.
            if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
            {
                return true;
            }

            // If RemoteCertificateChainErrors, walk the chain to handle errors.
            if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
            {
                if (chain != null && chain.ChainStatus != null)
                {
                    foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus)
                    {
                        if ((certificate.Subject == certificate.Issuer) &&
                           (status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot))
                        {
                            // Untrusted root for self-signed certificate is OK.
                            continue;
                        }
                        else
                        {
                            if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError)
                            {
                                ConsoleTrace("!! Chain Error status='" + status.Status.ToString() +
                                          "', statusInformation='" + status.StatusInformation + "'");
                                // Fail on any other certificate error.
                                return false;
                            }
                        }
                    }
                }

                // No errors except untrusted root for self-signed certificates.
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Case insensitive check if the collection contains the string.
        /// </summary>
        /// <param name="collection">The collection of objects, only strings are checked</param>
        /// <param name="match">String to match</param>
        /// <returns>true, if match contains in the collection</returns>
        private static bool Contains(System.Collections.ICollection collection, string match)
        {
            foreach (object obj in collection)
            {
                string str = obj as string;
                if (str != null)
                {
                    if (string.Compare(str, match, true) == 0)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Check maxHops, if less than one - throw exception, otherwise - decrement the counter.
        /// </summary>
        /// <param name="maxHops">Hops counter</param>
        private static void CheckAndDecrementHops(ref int maxHops)
        {
            if (maxHops <= 0)
            {
                ConsoleTrace("!! The number of hops exceeded the limit");
                throw new System.ArgumentOutOfRangeException("maxHops", "The number of hops exceeded the limit");
            }
            maxHops = maxHops - 1;
        }

        /// <summary>
        /// Check if AutodiscoverResponse contains Outlook data.
        /// </summary>
        /// <param name="autodiscoverResponse">Autodiscover response to verify</param>
        /// <returns>true if OutlookDate is presented</returns>

        private static bool IsOutlookDataReturned(AutodiscoverResponseXml autodiscoverResponse)
        {
            return ((autodiscoverResponse != null) &&
                  (autodiscoverResponse.OutlookData != null));
        }

        /// <summary>
        /// Check if AutodiscoverResponse contains Error data.
        /// </summary>
        /// <param name="autodiscoverResponse">Autodiscover response to verify</param>
        /// <returns>true if ErrorResponse is presented</returns>

        private static bool IsErrorResponseReturned(AutodiscoverResponseXml autodiscoverResponse)
        {
            return ((autodiscoverResponse != null) &&
                  (autodiscoverResponse.ErrorResponse != null));
        }

        /// <summary>
        /// Console Tracing function.
        /// </summary>
        /// <param name="message">Message to output to Console</param>
        private static void ConsoleTrace(string message)
        {
            if (TraceToConsole)
                System.Console.WriteLine(message);
        }

        /// <summary>
        /// Generate an AutoDiscover XML Request.
        /// </summary>
        /// <param name="emailAddress">E-mail address to Autodiscover</param>
        /// <param name="requestStream">Output stream for serialized Autodiscover request</param>
        private static void SerializeRequest(string emailAddress, System.IO.Stream requestStream)
        {
            AutodiscoverRequestXml requestXml =
               AutodiscoverRequestXml.NewRequest(emailAddress);

            System.Xml.Serialization.XmlSerializer xmlSerializer =
               new System.Xml.Serialization.XmlSerializer(typeof(AutodiscoverRequestXml));

            xmlSerializer.Serialize(requestStream, requestXml);
        }

        /// <summary>
        /// Parse an AutoDiscover XML Response.
        /// </summary>
        /// <param name="responseStream">Stream to deserialize</param>
        /// <returns>Deserialized AutodiscoverResponse class instance</returns>

        private static AutodiscoverResponseXml DeserializeResponse(System.IO.Stream responseStream)
        {
            System.Xml.Serialization.XmlSerializer xmlDeserializer =
               new System.Xml.Serialization.XmlSerializer(typeof(AutodiscoverResponseXml));

            return xmlDeserializer.Deserialize(responseStream) as AutodiscoverResponseXml;
        }

        #region Autodiscover Request Xml

        const string OutlookRequestNamespace = "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006";
        const string OutlookResponseNamespace = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
        const string AutodiscoverResponseNamespace = "http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006";

        /*   Autodiscover Request Xml sample
		  
          <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
            <Request>
              <EMailAddress>user@contoso.com</EMailAddress>
              <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
            </Request>
          </Autodiscover>

         */

        /// <summary></summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Autodiscover", Namespace = OutlookRequestNamespace)]
        public class AutodiscoverRequestXml
        {
            /// <summary></summary>
            [System.Xml.Serialization.XmlElement(IsNullable = false)]
            public RequestXml Request = null;

            /// <summary>Creates Autodiscover XML request</summary>
            public static AutodiscoverRequestXml NewRequest(string address)
            {
                RequestXml request = new RequestXml();
                request.EMailAddress = address;
                request.AcceptableResponseSchema = OutlookResponseNamespace;

                AutodiscoverRequestXml outlookRequest = new AutodiscoverRequestXml();
                outlookRequest.Request = request;

                return outlookRequest;
            }
        }

        /// <summary>Request container</summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Request", Namespace = OutlookRequestNamespace)]
        public class RequestXml
        {
            /// <summary>User SMTP address</summary>
            public string EMailAddress = null;

            /// <summary>The response schema, acceptable for a client</summary>
            public string AcceptableResponseSchema = null;
        }

        #endregion

        #region Autodiscover Response Xml

        /* Autodiscover sample response

		<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
			<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
				<User>
					<DisplayName>First Last</DisplayName>
					<LegacyDN>/o=contoso/ou=First Administrative Group/cn=Recipients/cn=iuser885646</LegacyDN>
					<DeploymentId>644560b8-a1ce-429c-8ace-23395843f701</DeploymentId>
				</User>
				<Account>
					<AccountType>email</AccountType>
					<Action>settings</Action>
					<Protocol>
						<Type>EXCH</Type>
						<Server>MBX-SERVER.mail.internal.contoso.com</Server>
						<ServerDN>/o=contoso/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Configuration/cn=Servers/cn=MBX-SERVER</ServerDN>
						<ServerVersion>72008287</ServerVersion>
						<MdbDN>/o=contoso/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Configuration/cn=Servers/cn=MBX-SERVER/cn=Microsoft Private MDB</MdbDN>
						<ASUrl>https://mail.contoso.com/ews/exchange.asmx</ASUrl>
						<OOFUrl>https://mail.contoso.com/ews/exchange.asmx</OOFUrl>
						<UMUrl>https://mail.contoso.com/unifiedmessaging/service.asmx</UMUrl>
						<OABUrl>https://mail.contoso.com/OAB/d29844a9-724e-468c-8820-0f7b345b767b/</OABUrl>
					</Protocol>
					<Protocol>
						<Type>EXPR</Type>
						<Server>Exchange.contoso.com</Server>
						<ASUrl>https://mail.contoso.com/ews/exchange.asmx</ASUrl>
						<OOFUrl>https://mail.contoso.com/ews/exchange.asmx</OOFUrl>
						<UMUrl>https://mail.contoso.com/unifiedmessaging/service.asmx</UMUrl>
						<OABUrl>https://mail.contoso.com/OAB/d29844a9-724e-468c-8820-0f7b345b767b/</OABUrl>
					</Protocol>
					<Protocol>
						<Type>WEB</Type>
						<Internal>
							<OWAUrl AuthenticationMethod="Ntlm, WindowsIntegrated">https://cas-01-server.mail.internal.contoso.com/owa</OWAUrl>
							<OWAUrl AuthenticationMethod="Ntlm, WindowsIntegrated">https://cas-02-server.mail.internal.contoso.com/owa</OWAUrl>
							<OWAUrl AuthenticationMethod="Basic">https://cas-04-server.mail.internal.contoso.com/owa</OWAUrl>
							<OWAUrl AuthenticationMethod="Ntlm, WindowsIntegrated">https://cas-05-server.mail.internal.contoso.com/owa</OWAUrl>
						</Internal>
					</Protocol>
				</Account>
			</Response>
		</Autodiscover>
		*/

        /// <summary>Autodiscover response container</summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Autodiscover", Namespace = AutodiscoverResponseNamespace)]
        public class AutodiscoverResponseXml
        {
            /// <summary>Outlook data container</summary>
            [System.Xml.Serialization.XmlElement(IsNullable = false, ElementName = "Response", Namespace = OutlookResponseNamespace)]
            public OutlookDataXml OutlookData = null;

            /// <summary>Error response</summary>
            [System.Xml.Serialization.XmlElement(IsNullable = false, ElementName = "Response", Namespace = AutodiscoverResponseNamespace)]
            public ErrorResponseXml ErrorResponse = null;
        }

        /// <summary>Outlook data response container</summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Response", Namespace = OutlookResponseNamespace)]
        public class OutlookDataXml
        {
            /// <summary>User data container</summary>
            public UserXml User = null;
            /// <summary>Account data container</summary>
            public AccountXml Account = null;
        }

        /// <summary>User container</summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "User", Namespace = OutlookResponseNamespace)]
        public class UserXml
        {
            /// <summary>User display name</summary>
            public string DisplayName = null;
            /// <summary>Exchange LegacyDN address</summary>
            public string LegacyDN = null;
            /// <summary>Deployment Id</summary>
            public string DeploymentId = null;
        }

        /// <summary>Account container</summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Account", Namespace = OutlookResponseNamespace)]
        public class AccountXml
        {
            /// <summary></summary>
            public string AccountType = null;
            /// <summary></summary>
            public string Action = null;
            /// <summary></summary>
            public string RedirectAddr = null;
            /// <summary></summary>
            [System.Xml.Serialization.XmlElement]
            public ProtocolXml[] Protocol = null;
            /// <summary></summary>
            public string SSL = null;
            /// <summary></summary>
            public string AuthPackage = null;
        }

        /// <summary>Protocol container</summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Protocol", Namespace = OutlookResponseNamespace)]
        public class ProtocolXml
        {
            /// <summary></summary>
            public string Type = null;
            /// <summary></summary>
            public string Server = null;
            /// <summary></summary>
            public string ServerDN = null;
            /// <summary></summary>
            public string ServerVersion = null;
            /// <summary></summary>
            public string MdbDN = null;
            /// <summary></summary>
            public string ASUrl = null;
            /// <summary></summary>
            public string OOFUrl = null;
            /// <summary></summary>
            public string OABUrl = null;
            /// <summary></summary>
            public string UMUrl = null;
            /// <summary></summary>
            public int Port = 0;
            /// <summary></summary>
            public int DirectoryPort = 0;
            /// <summary></summary>
            public int ReferralPort = 0;
            /// <summary></summary>
            public string FBPublish = null;
            /// <summary></summary>
            public string SSL = null;
            /// <summary></summary>
            public string TTL = null;
            /// <summary></summary>
            public string AuthPackage = null;
            /// <summary></summary>
            public string CertPrincipalName = null;
            /// <summary></summary>
            [System.Xml.Serialization.XmlAnyElement]
            public object[] OtherXml = null;
        }

        /// <summary></summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Response", Namespace = AutodiscoverResponseNamespace)]
        public class ErrorResponseXml
        {
            /// <summary></summary>
            public ErrorXml Error = null;
        }

        /// <summary>Error container</summary>
        [System.Xml.Serialization.XmlRoot(ElementName = "Error", Namespace = AutodiscoverResponseNamespace)]
        public class ErrorXml
        {
            /// <summary></summary>
            [System.Xml.Serialization.XmlAttribute]
            public string Time = null;
            /// <summary></summary>
            [System.Xml.Serialization.XmlAttribute]
            public string Id = null;
            /// <summary></summary>
            public string ErrorCode = null;
            /// <summary></summary>
            public string Message = null;
            /// <summary></summary>
            public string DebugData = null;
        }
        #endregion

        /// <summary>
        /// Returns local computer's site name.
        /// </summary>
        /// <returns>Name of the local site.</returns>

        private static string GetSiteName()
        {
            System.DirectoryServices.ActiveDirectory.ActiveDirectorySite site =
               System.DirectoryServices.ActiveDirectory.ActiveDirectorySite.GetComputerSite();
            return site.Name;
        }

    }
}
